package com.cfushn.ddd.tools.crypto;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.crypto.AEADBadTagException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * AesUtil
 *
 * @author CFuShn 2022/11/22 11:08
 */
@SuppressWarnings("unused")
public class AesUtil {

    private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";
    private static final int TAG_LENGTH_BIT = 128;
    private static final int IV_LENGTH_BYTE = 12;
    private static final Charset UTF_8 = StandardCharsets.UTF_8;
    private static final int COMMON_BYTE = 80;

    private static SecretKey transferKey(String secretKey) {
        byte[] decodedKey = Base64.getDecoder().decode(secretKey);
        return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
    }

    public static byte[] encrypt(byte[] pText, String secretKey, byte[] iv) throws Exception {
        SecretKey secret = transferKey(secretKey);
        Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
        cipher.init(Cipher.ENCRYPT_MODE, secret, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
        return cipher.doFinal(pText);
    }

    /**
     * prefix IV length + IV bytes to cipher text
     *
     * @param pText     byte
     * @param secretKey String
     * @param iv        byte
     * @return byte[]
     */
    public static byte[] encryptWithPrefixIv(byte[] pText, String secretKey, byte[] iv)
        throws Exception {

        byte[] cipherText = encrypt(pText, secretKey, iv);

        return ByteBuffer.allocate(iv.length + cipherText.length).put(iv).put(cipherText).array();

    }

    public static String decrypt(byte[] cText, String secretKey, byte[] iv) throws Exception {
        SecretKey secret = transferKey(secretKey);
        Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
        cipher.init(Cipher.DECRYPT_MODE, secret, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
        byte[] plainText;
        try {
            plainText = cipher.doFinal(cText);
        } catch (AEADBadTagException e) {
            System.out.println("javax.crypto.AEADBadTagException: Tag mismatch!");
            return "";
        }

        return new String(plainText, UTF_8).trim();

    }

    public static String decryptWithPrefixIv(byte[] cText, int length, String secretKey)
        throws Exception {
        if (cText == null || cText.length < length) {
            return "";
        }
        byte[] temp = new byte[length];
        System.arraycopy(cText, 0, temp, 0, length);

        ByteBuffer bb = ByteBuffer.wrap(temp);
        byte[] iv = new byte[IV_LENGTH_BYTE];
        bb.get(iv);
        byte[] cipherText = new byte[bb.remaining()];
        bb.get(cipherText);
        return decrypt(cipherText, secretKey, iv);
    }

    public static String decryptWithPrefixIvForCommon(byte[] cText, String secretKey)
        throws Exception {
        return decryptWithPrefixIv(cText, COMMON_BYTE, secretKey);
    }

}
